Skip to content

Method: processClass(String, Set)

1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.loaders;
16:
17: import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties;
18: import cz.cvut.kbss.jopa.model.annotations.OWLClass;
19: import cz.cvut.kbss.jopa.utils.Configuration;
20: import org.slf4j.Logger;
21: import org.slf4j.LoggerFactory;
22:
23: import java.io.File;
24: import java.io.IOException;
25: import java.net.URI;
26: import java.net.URISyntaxException;
27: import java.net.URL;
28: import java.util.Enumeration;
29: import java.util.HashSet;
30: import java.util.Objects;
31: import java.util.Set;
32: import java.util.jar.JarEntry;
33: import java.util.jar.JarFile;
34:
35: public class EntityLoader {
36:
37: private static final Logger LOG = LoggerFactory.getLogger(EntityLoader.class);
38:
39: private static final String JAR_FILE_SUFFIX = ".jar";
40: private static final String CLASS_FILE_SUFFIX = ".class";
41:
42: /**
43: * Discovers and returns all entity classes within the scan package and its subpackages.
44: * <p>
45: * I.e., it looks for classes annotated with {@link OWLClass}.
46: *
47: * @param configuration Persistence configuration, should contain value for the {@link
48: * JOPAPersistenceProperties#SCAN_PACKAGE} property
49: * @return Set of entity classes
50: * @throws IllegalArgumentException If {@link JOPAPersistenceProperties#SCAN_PACKAGE} values is missing
51: */
52: public Set<Class<?>> discoverEntityClasses(Configuration configuration) {
53: Objects.requireNonNull(configuration);
54: if (!configuration.contains(JOPAPersistenceProperties.SCAN_PACKAGE)) {
55: throw new IllegalArgumentException(
56: "Missing the " + JOPAPersistenceProperties.SCAN_PACKAGE + " property.");
57: }
58: String toScan = configuration.get(JOPAPersistenceProperties.SCAN_PACKAGE);
59: if (toScan.isEmpty()) {
60: throw new IllegalArgumentException(JOPAPersistenceProperties.SCAN_PACKAGE + " property cannot be empty.");
61: }
62: return discoverEntities(toScan);
63: }
64:
65:
66: /**
67: * Using code from https://github.com/ddopson/java-class-enumerator
68: */
69: private Set<Class<?>> discoverEntities(String scanPath) {
70: final Set<Class<?>> all = new HashSet<>();
71: final ClassLoader loader = Thread.currentThread().getContextClassLoader();
72: try {
73: Enumeration<URL> urls = loader.getResources(scanPath.replace('.', '/'));
74: while (urls.hasMoreElements()) {
75: final URL url = urls.nextElement();
76: if (url.toString().startsWith("jar:")) {
77: processJarFile(url, scanPath, all);
78: } else {
79: processDirectory(new File(getUrlAsUri(url).getPath()), scanPath, all);
80: }
81: }
82: } catch (IOException e) {
83: throw new JopaInitializationException("Unable to scan packages for entity classes.", e);
84: }
85: if (all.isEmpty()) {
86: LOG.warn("No entity classes found in package " + scanPath);
87: }
88: return all;
89: }
90:
91: private URI getUrlAsUri(URL url) {
92: try {
93: // Transformation to URI handles encoding, e.g. of whitespaces in the path
94: return url.toURI();
95: } catch (URISyntaxException ex) {
96: throw new JopaInitializationException(
97: "Unable to scan resource " + url + ". It is not a valid URI.", ex);
98: }
99: }
100:
101: private void processJarFile(URL jarResource, String packageName, Set<Class<?>> entityClasses) {
102: final String relPath = packageName.replace('.', '/');
103: final String jarPath = jarResource.getPath().replaceFirst("[.]jar[!].*", JAR_FILE_SUFFIX)
104: .replaceFirst("file:", "");
105:
106: LOG.trace("Scanning jar file {} for entity classes.", jarPath);
107: try (final JarFile jarFile = new JarFile(jarPath)) {
108: final Enumeration<JarEntry> entries = jarFile.entries();
109: while (entries.hasMoreElements()) {
110: final JarEntry entry = entries.nextElement();
111: final String entryName = entry.getName();
112: String className = null;
113: if (entryName.endsWith(CLASS_FILE_SUFFIX) && entryName.startsWith(relPath)) {
114: className = entryName.replace('/', '.').replace('\\', '.').replace(CLASS_FILE_SUFFIX, "");
115: }
116: if (className != null) {
117: processClass(className, entityClasses);
118: }
119: }
120: } catch (IOException e) {
121: throw new JopaInitializationException("Unexpected IOException reading JAR File " + jarPath, e);
122: }
123: }
124:
125: private void processClass(String className, Set<Class<?>> entityClasses) {
126: try {
127: final Class<?> cls = Class.forName(className);
128:• if (cls.getAnnotation(OWLClass.class) != null) {
129: entityClasses.add(cls);
130: }
131: } catch (ClassNotFoundException e) {
132: throw new JopaInitializationException("Unexpected ClassNotFoundException when scanning for entities.", e);
133: }
134: }
135:
136: private void processDirectory(File dir, String packageName, Set<Class<?>> entityClasses) {
137: LOG.trace("Scanning directory {} for entity classes.", dir);
138: // Get the list of the files contained in the package
139: final String[] files = dir.list();
140: if (files == null) {
141: return;
142: }
143: for (String fileName : files) {
144: String className = null;
145: // we are only interested in .class files
146: if (fileName.endsWith(CLASS_FILE_SUFFIX)) {
147: // removes the .class extension
148: className = packageName + '.' + fileName.substring(0, fileName.length() - 6);
149: }
150: if (className != null) {
151: processClass(className, entityClasses);
152: }
153: final File subDir = new File(dir, fileName);
154: if (subDir.isDirectory()) {
155: processDirectory(subDir, packageName + '.' + fileName, entityClasses);
156: }
157: }
158: }
159: }